# coding=utf-8
from string import Template
import os
from typing import List
from parse.parse_types import  Module
from parse.sdb_parser import ECU
from utils.payload import TypeEnum, TypeEnum

from utils.util import writeFile



IPCPSignalManager_h = Template("""
#pragma once

#include <cstdint>
#include <array>
#include <string>
#include <memory>
#include <transfer/ITransfer.h>
#include "signal_id.h"
${includes}
namespace ecarx::transfer{

${typedef}
class IPCPSignalManager{
    protected:
        std::shared_ptr<ITransfer> mIPCPConn;
    public:
    IPCPSignalManager(std::shared_ptr<ITransfer> conn):mIPCPConn(conn){

    }

    template <typename CarConfig, typename... Args>
    inline bool carConfigIs(CarConfig carConfig, Args... args) {
        std::optional<Signal> signal;
        if (mIPCPConn != nullptr) {
            signal = mIPCPConn->read(static_cast<int32_t>(carConfig));
        }
        if (signal.has_value()) {
            return ((static_cast<int32_t>(args) == static_cast<int32_t>(signal->value.int32Values[0])) || ...);
        }
        return false;
    }

    template <typename... Args>
    inline bool carModeIs(Args... arg) {
        sdb::CarModSts1 carMode = getVehModMngtGlbSafe1CarModSts1();
        return ((arg == carMode) || ...);
    }

    template <typename... Args>
    inline bool usgModeIs(Args... args) {
        sdb::UsgModSts1 usgMode = getVehModMngtGlbSafe1UsgModSts();
        return ((args == usgMode) || ...);
    }

    ${methods};
};
}
""")

IPCPSignalManager_cpp = Template("""
#include "ap/IPCPSignalManager.h"
#include <transfer/utils.h>

namespace ecarx::transfer{

${impl} 
}
""")

get_imp_template = Template(
"""
    ${signature}{
        std::optional<Signal> signal;
        if(mIPCPConn != nullptr){
            signal=mIPCPConn->read(static_cast<int32_t>(${signalId}));
        }
        if (signal.has_value()){
            ${returnValue}
        }
        return ${default};
    }
"""
)

set_base_type_template = Template(
"""
    ${signature}{
        Signal signal;
        signal.signalId=static_cast<uint32_t>(${signalId});
        signal.value.${member}={${convert_type}value};
        if(mIPCPConn != nullptr){
            mIPCPConn->write(signal);
        }
    }
"""
)

set_struct_template = Template(
"""
    ${signature}{
        Signal signal;
        signal.signalId=static_cast<uint32_t>(${signalId});
        auto bytes = utils::toBytes(value);
        signal.value.bytes=(std::vector<uint8_t>)bytes;
        if(mIPCPConn != nullptr){
            mIPCPConn->write(signal);
        }
    }
"""
)

set_array_template = Template(
"""
    ${signature}{
        Signal signal;
        signal.signalId=static_cast<uint32_t>(${signalId});
        signal.value.${member}.resize(${array_len},0);
        for(size_t i=0;i<${array_len};i++){
            signal.value.${member}[i]=value[i];
        }
        if(mIPCPConn != nullptr){
            mIPCPConn->write(signal);
        }
    }
"""
)



set_other_template = Template(
"""
    ${signature}{
        
    }
"""
)


def gen_IPCPSignalManager(ecus:List[ECU],modules:List[Module],outDir:str):
    method_signatures =[]
    method_get_set=[]
    includes =[]
    typedef = []
    for ecu in ecus:
        if ecu.hasWriteSignals():
            includes.append("#include \"vp/{}_TX.h\"".format(ecu.name.upper()))
        if ecu.hasReadSignals():
            includes.append("#include \"vp/{}_RX.h\"".format(ecu.name.upper()))
        for tx in sorted(ecu.txSignals, key=lambda group: group.name):
            param_type = tx.getSignalMethodParamType()
            
            method_signature = "void set{signal_name}({ptype} value) const".format(signal_name=tx.name,ptype=param_type)
            method_define = "void IPCPSignalManager::set{signal_name}({ptype} value) const".format(signal_name=tx.name,ptype=param_type)
            if not tx.isSingle():
                typedef.append("typedef Op{0}_SetRequestNoReturn {0};".format(tx.name))
            method_signatures.append(method_signature)
            if tx.isSingle():
                payloadType = tx.signals[0].getValueType()
                hasEnum = tx.signals[0].hasEnum()
                isFloat = tx.signals[0].isFloat()
                method_get_set.append(set_base_type_template.substitute(
                    signature=method_define,
                    signalId = "SignalTX::{}".format(tx.name),
                    convert_type = "(int32_t)".format(payloadType.name) if (hasEnum or payloadType == TypeEnum.uint32_t ) and not isFloat else "",
                    member= tx.toSignalObjMember()
                ))
            else:
                method_get_set.append(set_struct_template.substitute(
                    signature=method_define,
                    signalId = "SignalTX::{}".format(tx.name)
                ))
        for signal in ecu.rxPackage.signals:

            returnType = signal.getSignalMethodReturnType()
            method_signature = "{return_type} get{signal_name}() const".format(signal_name=signal.name,return_type=returnType)
            method_define = "{return_type} IPCPSignalManager::get{signal_name}() const ".format(signal_name=signal.name,return_type=returnType)
            if signal.hasEnum():
                default="sdb::{}(-1)".format(signal.getEncodeType().getTypeName())
            else:
                default = "-1"

            def returnValue():
                if signal.isFloat():
                    return "return signal->value.floatValues[0];"
                else:
                    if signal.hasEnum():
                        return "return sdb::{}(signal->value.int32Values[0]);".format(signal.getEncodeType().getTypeName())
                    else:
                        return "return signal->value.int32Values[0];"
                    
            method_signatures.append(method_signature)
            method_get_set.append(get_imp_template.substitute(
                signature=method_define,
                signalId = "SignalRX::{}".format(signal.name),
                returnValue = returnValue(),
                default = default
            ))
    includes.append("#include \"vp/pafunctype.h\"")
    for module in modules:
        if module.name.lower() == "carconfig":continue
        includes.append("#include \"vp/{}.h\"".format(module.name))
        for cb in module.cb_apis:
            payload = cb.getPayload()
            method_signatures.append("void set{signal_name}({ptype}) const".format(
                signal_name=cb.api,
                ptype=cb.getSetMethodParamType()
            ))
            
            method_define = "void IPCPSignalManager::set{signal_name}({ptype} value) const".format(signal_name=cb.api,ptype=cb.getSetMethodParamType())
            if payload.isBaseTypePayload():
                basePayload = payload.getBaseTypePayload()
                method_get_set.append(set_base_type_template.substitute(
                    signature=method_define,
                    signalId = "CBSignal::{}".format(cb.api),
                    member=cb.toSignalObjMember(),
                    convert_type="(int32_t)" if basePayload.hasEncodeType() or basePayload.type == TypeEnum.uint32_t else ""
                ))
            elif payload.isStructPayload():
                method_get_set.append(set_struct_template.substitute(
                    signature=method_define,
                    signalId = "CBSignal::{}".format(cb.api)
                ))
            elif payload.isArrayPayload():
                method_get_set.append(set_array_template.substitute(
                    signature=method_define,
                    signalId = "CBSignal::{}".format(cb.api),
                    member=cb.toSignalObjMember(),
                    array_len=payload.getArrayPayload().array_len
                ))
            else:
                method_get_set.append(set_other_template.substitute(
                        signature=method_define
                ))
        
        for pa in module.pa_apis:
            payload = pa.getPayload()
            if payload.isStructPayload():
                structPayload = payload.getStructPayload()
                pa_method_signature = "{type} get{signal_name}() const".format(signal_name=pa.api,type=structPayload.type_name)
                method_define = "{type} IPCPSignalManager::get{signal_name}() const ".format(signal_name=pa.api,type=structPayload.type_name)
                method_signatures.append(pa_method_signature)
                method_get_set.append(get_imp_template.substitute(
                    signature=method_define,
                    signalId = "PASignal::{}".format(pa.api),
                    returnValue = "return utils::toValue<{}>(signal->value.bytes);".format(structPayload.type_name),
                    default = "{0}{{}}".format(structPayload.type_name)
                ))

    writeFile(os.path.join(outDir,"include/ap"), "IPCPSignalManager.h",lambda file:
        file.write(IPCPSignalManager_h.substitute(methods=";\n\t".expandtabs(4).join(method_signatures),
            includes="\n".join(includes),
            typedef="\n".join(typedef)
            )))

    writeFile(os.path.join(outDir,"src"), "IPCPSignalManager.cpp",lambda file:
        file.write(IPCPSignalManager_cpp.substitute(
            impl= "".join(method_get_set)
        )))


